import os
import pickle
import random
import re
import string
import warnings
from base64 import urlsafe_b64decode, urlsafe_b64encode
from binascii import a2b_hex, b2a_hex
from decimal import ROUND_HALF_UP, Decimal
from hashlib import md5
from pathlib import Path
from typing import Any, Callable, Optional, Union

CODE_CHARS = list(set(string.ascii_uppercase + string.digits) - set("IOZ012"))


def gen_nonce(length: int = 5, chars: Union[list, str] = CODE_CHARS) -> str:
    """生成随机字符串

    :param length: 要生成的随机串长度
    :param chars: 随机字符串的取值范围, 默认为大写字母+数字去掉易混淆的IOZ012

    Usage::
        In [1]: gen_nonce()
        Out[1]: 'HG356'
        In [2]: gen_nonce(6)
        Out[2]: 'XGY973'
    """
    return "".join(random.choices(chars, k=length))


class AttrDict(dict):
    """属性字典：一个简单又好用的类

    Example::
        In [1]: d = {'a': 1, 'b': 2}
        In [2]: attr_dict = AttrDict(d)
        In [3]: assert d['a'] == attr_dict.a == attr_dict['a']
        In [4]: assert d['b'] == attr_dict.b == attr_dict['b']
        In [5]: assert json.dumps(d) == json.dumps(attr_dict)
    """

    def __init__(self, *args, **kwargs):
        super(AttrDict, self).__init__(*args, **kwargs)
        self.__dict__ = self


class LocalDict(dict):
    """本地字典：dict(a=a, b=b) == LocalDict('a b', locals())
    :param attrs: 要生成的字典的键, 如：'key1 key2 key3'
    :param god: 值的来源，如：locals()

    Usage:
        >>> a, b = 1, 2
        >>> LocalDict('a b', locals())
        {'a': 1, 'b': 2}
    """

    def __new__(cls, attrs: str, god: dict):
        return {i: god[i] for i in attrs.split()}


def get_client_ip(request):
    """get the ip of the visitor"""
    x_forwarded_for = request.META.get("HTTP_X_FORWARDED_FOR")
    if x_forwarded_for:
        ip = x_forwarded_for.split(",")[0]
    else:
        ip = request.META.get("REMOTE_ADDR")
    return ip


def run_and_echo(cmd: str, func: Optional[Callable] = None):
    """运行cmd，并打印运行的语句"""
    print("-->", cmd)
    if func is None:
        func = os.system
    return func(cmd)


def resolve_url(request, pk_xx: str, url_name: str = "notify", app_name=None):
    """get absolute url as: https://www.host.com/wechats/notify/{pk_xx}/"""
    from django.urls import reverse

    warnings.warn(
        "`resolve_url` is deprecated and will remove in 1.5",
        DeprecationWarning,
    )
    app_name = app_name or request.resolver_match.app_name
    url_name = f"{app_name}:{url_name}"
    return request.build_absolute_uri(reverse(url_name, args=(pk_xx,)))


def parse_version():
    pattern = r'__version__ = "(.+)"'
    p = Path(__file__).parent / "__version__.py"
    return re.search(pattern, p.read_text()).group(1)


def be_star(s):
    """给敏感数据加星星
    Usage::
        >>> be_star('李时珍')
        '李**'
        >>> be_star(18620502007)
        '18*******07'
    """
    if not s:
        return s
    s = str(s)
    length = len(s)
    if length < 7:  # for name
        return s[0] + "*" * (length - 1)
    if length > 200:  # for long secret
        head, tail = 50, -45
        length = 6
    else:  # for phone or short secret
        head, tail = 2, -2
        length = len(s) - head + tail
    return f'{s[:head]}{"*"*length}{s[tail:]}'


def star_attr(obj, *args):
    for attr in args:
        setattr(obj, attr, be_star(getattr(obj, attr)))
    return obj


def is_alipay(request):
    """check whether the browser if alipay"""
    return "AlipayClient/" in request.META.get("HTTP_USER_AGENT", "")


def is_wechat(request):
    """check whether the browser is wechat"""
    return "MicroMessenger/" in request.META.get("HTTP_USER_AGENT", "")


def is_qq(request):
    """check whether the browser is qq"""
    return " QQ/" in request.META.get("HTTP_USER_AGENT", "")


def get_scan_type(request):
    """tell the scan tool"""
    if is_wechat(request) or is_qq(request):
        return "wechat"
    if is_alipay(request):
        return "alipay"
    return "unknown"


def pk_encrypted(pk):
    """19 -> 4d546b3d"""
    return b2a_hex(urlsafe_b64encode(str(pk).encode())).decode()


def decrypt_pk(s):
    """4d546b3d -> 19"""
    return urlsafe_b64decode(a2b_hex(s)).decode()


def to_decimal(
    amount: Union[int, str, float, Decimal], decimal_places: int = 2
) -> Decimal:
    """将传入的数值，四舍五入转化为两个小数的Decimal对象
    :param decimal_places: 要保留多少为小数

    Usage::
        >>> to_decimal(1.565)
        Decimal('1.57')
        >>> to_decimal('1.564')
        Decimal('1.56')
        >>> to_decimal('1.564', 1)
        Decimal('1.6')
    """
    return Decimal(str(amount)).quantize(
        Decimal("0." + "0" * decimal_places), ROUND_HALF_UP
    )


def dump_md5(data: Any) -> str:
    """将数据序列化后,计算md5

    Usage::
        >>> dump_md5({'a': 1})
        '34a63eb04909cb55ad3d516892c91859'
        >>> dump_md5([{'a': 1}, {'a': 2}])
        '8ed8f8f9a2781054980fb7ce237516f3'
    """
    return md5(pickle.dumps(data)).hexdigest()
